<!DOCTYPE html>
<html lang="zh-CN">





<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/myblog/img/apple-touch-icon.png">
  <link rel="icon" type="image/png" href="/myblog/img/favicon.png">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="description" content="TakCode Blog">
  <meta name="author" content="谢芃">
  <meta name="keywords" content="">
  <title>微信小程序学习笔记 - TakCode</title>

  <link  rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/5.12.1/css/all.min.css" />
<link  rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<link  rel="stylesheet" href="https://cdn.staticfile.org/mdbootstrap/4.13.0/css/mdb.min.css" />
<link  rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/3.0.1/github-markdown.min.css" />

<link rel="stylesheet" href="//at.alicdn.com/t/font_1067060_qzomjdt8bmp.css">



  <link  rel="stylesheet" href="/myblog/lib/prettify/tomorrow-night-eighties.min.css" />

<link  rel="stylesheet" href="/myblog/css/main.css" />


  <link defer rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css" />


<!-- 自定义样式保持在最底部 -->


<meta name="generator" content="Hexo 4.2.0"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand"
       href="/myblog/">&nbsp;<strong>TakCode</strong>&nbsp;</a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/myblog/">首页</a>
          </li>
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/myblog/archives/">归档</a>
          </li>
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/myblog/categories/">分类</a>
          </li>
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/myblog/tags/">标签</a>
          </li>
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/myblog/about/">关于</a>
          </li>
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;&nbsp;<i
                class="iconfont icon-search"></i>&nbsp;&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="view intro-2" id="background" parallax=true
         style="background: url('/myblog/img/default.png') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask rgba-black-light flex-center">
          <div class="container text-center white-text fadeInUp">
            <span class="h2" id="subtitle">
              
            </span>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      <div class="container nopadding-md">
        <div class="py-5 z-depth-3" id="board"
          >
          
          <div class="container">
            <div class="row">
              <div class="col-12 col-md-10 m-auto">
                

<div class="page-content">
  <p>微信小程序学习笔记</p>
<a id="more"></a>
<h1 id="开篇：微信小程序概述"><a href="#开篇：微信小程序概述" class="headerlink" title="开篇：微信小程序概述"></a>开篇：微信小程序概述</h1><h2 id="什么是微信小程序"><a href="#什么是微信小程序" class="headerlink" title="什么是微信小程序"></a>什么是微信小程序</h2><p>小程序是有别于传统 Web App、HTML5、原生 App 以及微信公众号的一种新的应用形式，由微信团队于 2017 年 1 月对外发布的，经过近两年的发展，小程序越来越成熟，小程序研发也越来越受到重视。什么是小程序？用张小龙自己的话来说：</p>
<blockquote>
<p>小程序是一种不需要下载安装即可使用的应用，它实现了应用“触手可及”的梦想，用户扫一扫或者搜一下即可打开应用。也体现了“用完即走”的理念，用户不用关心是否安装太多应用的问题。应用将无处不在，随时可用，但又无须安装卸载。</p>
</blockquote>
<p>作为微信公众平台的组成部分，小程序是服务号、订阅号的功能延伸。微信小程序从 2017 年 1 月 9 日对外开放以来，根据腾讯的数据，截止到 2018 年 7 月 11 日，微信小程序已上线超过 100 万，已有超过 150 万个开发者、5000 多家第三方开发平台加入；在用户黏度数据上，人均每日打开小程序 4 次，54% 是用户主动打开的，从以上数据来看，微信小程序已经逐渐走进人们的生活，用户日常访问小程序的习惯也正在逐步养成，微信小程序成为连接各行各业的重要工具。据报道，2018 年小程序数量将突破 250 万，小程序数量超越 App Store 应用总和。笔者在一些招聘网站上分别以「小程序」和「小游戏」为关键字进行搜索，发现相关招聘职位有好几页，由此可见市场对小程序/小游戏开发者有很大的需求。</p>
<p>随着微信小程序搞得风风火火，阿里巴巴和百度也开始提出自己的小程序。2017 年 8 月，阿里巴巴旗下的支付宝小程序开始对外公测；2018 年 7 月在百度的 AI 大会上，依托百度 App 的百度智能小程序宣布开始内测。至此，BAT 三家都有了自己的小程序。除了 BAT 之外，小米联合众多手机开发厂商也在 2018 年年初发布了「快应用」，而谷歌也发布了「Instant Apps」。有人对比了 BAT 小程序和快应用的优缺点：</p>
<p>微信小程序作为第一个小程序的解决方案，他们在实现方案、接口标准上都占尽了先机，支付宝和百度在微信的后面一直追赶，这也是为什么支付宝和百度的小程序开发者文档中或者代码中会出现微信小程序的关键字。</p>
<p>当然，微信小程序自正式上线以来，一直在不断地开放平台能力，微信极有耐心地在为小程序逐步加温，笔者在<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/release.html" target="_blank" rel="noopener">小程序基础库更新日志</a>中发现，小程序基础库一直保持一月数次的密集发布，从日志来看，除了修复一些 bug 之外，微信小程序也在发布一些新功能。最近小程序和腾讯云团队打造了「小程序·云开发」的方案，将腾讯云和小程序开发紧密结合在一起，腾讯云为小程序开发者提供免费的云服务，通过小程序云服务开发者可以快速搭建小程序。</p>
<h2 id="如何进行微信小程序开发"><a href="#如何进行微信小程序开发" class="headerlink" title="如何进行微信小程序开发"></a>如何进行微信小程序开发</h2><p>微信小程序本质上是一套 Hybrid 解决方案，掌握基础 HTML5 知识的前端开发者就可以参与小程序的开发。微信小程序提供完善的开发工具链，熟悉客户端的开发者也可以快速上手小程序开发。如何快速开始小程序开发呢？分为下面几个步骤：</p>
<ul>
<li>注册一个小程序开发账号</li>
<li>下载微信开发者工具</li>
<li>学习开发语言基础知识和小程序框架原理</li>
<li>学习小程序 API 的使用</li>
<li>学习小程序调试和上线操作</li>
</ul>
<h1 id="基础篇-1：小程序开发基础知识"><a href="#基础篇-1：小程序开发基础知识" class="headerlink" title="基础篇 1：小程序开发基础知识"></a>基础篇 1：小程序开发基础知识</h1><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>小程序开发要先注册小程序账号，有了小程序账号才可以使用开发者工具。小程序是一种特殊的开发形式，里面的 API 和组件都是自己定制的，因此在普通的浏览器中不能预览，要预览功能和页面就需要使用开发者工具。</p>
<h3 id="注册小程序账号"><a href="#注册小程序账号" class="headerlink" title="注册小程序账号"></a>注册小程序账号</h3><p>先准备一个没有注册过公众号的邮箱，然后访问<a href="https://mp.weixin.qq.com/cgi-bin/wx?token=&lang=zh_CN" target="_blank" rel="noopener">小程序介绍页面</a>并点击底部的「前往注册」按钮，再按照提示填写个人信息，最后进入邮箱激活账号即可。</p>
<p>详细流程请参考<a href="https://developers.weixin.qq.com/miniprogram/dev/#%E7%94%B3%E8%AF%B7%E5%B8%90%E5%8F%B7" target="_blank" rel="noopener">官方文档</a>。</p>
<h3 id="安装开发者工具"><a href="#安装开发者工具" class="headerlink" title="安装开发者工具"></a>安装开发者工具</h3><p>小程序有自己的开发者工具，可以编写代码，实时查看页面和功能效果，还能在开发者工具中进行 debug。小程序开发者工具是使用 NW.js 编写的。</p>
<p>开发者工具下载地址：<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html" target="_blank" rel="noopener">微信开发者工具</a></p>
<h3 id="使用开发者工具演示-WeUI"><a href="#使用开发者工具演示-WeUI" class="headerlink" title="使用开发者工具演示 WeUI"></a>使用开发者工具演示 WeUI</h3><blockquote>
<p>WeUI 是一套同微信原生视觉体验一致的基础样式库，由微信官方设计团队为微信内网页和微信小程序量身设计，令用户的使用感知更加统一。包含 button、cell、dialog、 progress、 toast、article、actionsheet、icon 等各式组件。</p>
</blockquote>
<p>WeUI 有两个版本，一个是<a href="https://github.com/Tencent/weui" target="_blank" rel="noopener">普通的 HTML5 版本</a>，另外是<a href="https://github.com/Tencent/weui-wxss" target="_blank" rel="noopener">小程序版本</a>。本节我们通过 WeUI 来简单学习开发者工具的使用。</p>
<p>首先下载 WeUI 源码：</p>
<pre><code># https 方式（推荐）
git clone https://github.com/Tencent/weui-wxss.git
# 或者 ssh 方式
git clone git@github.com:Tencent/weui-wxss.git
</code></pre><p>打开开发者工具，使用注册时绑定的微信账号扫码登录，这时在进入的界面中选择「小程序项目」，如果是初次使用小程序开发者工具，没有创建过小程序项目，就会进入小程序项目管理页面，需要新建小程序项目</p>
<p>在这个页面，先选择我们 clone 下来的 WeUI 项目的 dist 文件夹。对于 AppID 选项，如果已经有了小程序账号，可以在账号后台找到 AppID 并填写上；若还没有注册小程序账号，可以直接在「或使用测试号：」后面单击「小程序」，就会自动填好。然后给项目起个名字，比如「WeUI 演示」，点击确定后就打开了 WeUI 项目。这时候看到的是开发者工具的开发界面，开发界面主要由三部分组成：模拟器、编辑器和调试器。</p>
<ul>
<li>模拟器：提供小程序的运行环境，模拟小程序在手机上的界面效果</li>
<li>编辑器：简单 IDE 功能，点击左侧树形菜单可以打开多个文件直接编写保存，做到实时预览效果，但是开发者工具的编辑器做得比较简单，而且使用体验并不好，建议选择自己顺手的 IDE 增加 WXML 和 WXSS 的语法高亮插件等来编辑代码</li>
<li>调试器：订制版的 Chrome 开发者工具，提供从页面结构到网络请求等多个面板支持，会用 Chrome DevTools 就很容易上手该工具</li>
</ul>
<blockquote>
<p><strong>Tips：</strong> 常用 IDE 推荐</p>
<ul>
<li>VS Code + <a href="https://marketplace.visualstudio.com/items?itemName=qiu8310.minapp-vscode" target="_blank" rel="noopener">minapp 插件</a></li>
<li>Sublime Text 3 + <a href="https://github.com/springlong/Sublime-wxapp/blob/master/docs/README.zh-ch.md" target="_blank" rel="noopener">Sublime wxapp 插件</a></li>
<li>Vim + <a href="https://github.com/chemzqm/wxapp.vim" target="_blank" rel="noopener">wxapp</a></li>
</ul>
</blockquote>
<p>除了三个重要组成部分之外，在开发者工具的顶部还有各种操作按钮。左侧主要是模拟器、编辑器、调试器和小程序云开发控制台的视图开关，可以控制对应视图的开启关闭。</p>
<p>中间部分是跟开发、编译、测试和上线相关的各种按钮，我们在开发和测试小程序中会经常使用，最常用的有：</p>
<ul>
<li>预览、远程调试：是可以在手机上直接预览效果，开启远程 debug 功能的</li>
<li>清缓存：对于一些授权登录、缓存、数据之类的操作，我们需要清理状态和数据，可以通过这个按钮操作</li>
<li>上传：如果是创建项目的时候填写了 AppID，那么会出现这个按钮，小程序开发完毕后可以通过这个来上传，上传之后可以在小程序后台申请测试版和审核，审核通过后就可以正式上线了</li>
</ul>
<p>最后介绍的是开发者工具右上角的「详情」。</p>
<p>详情下面主要有三个 Tab：项目设置、域名信息和腾讯云状态。</p>
<ul>
<li>项目设置：这个是用得比较多的，可以指定上传代码时候的编译情况，比如支持 ES6 语法、支持 css autoprefixer、代码压缩等</li>
<li>域名信息：小程序的 <code>request</code> 等访问的域名采用了白名单形式，在这里可以看到小程序管理后台设置的域名白名单</li>
<li>腾讯云状态：可以看到小程序账号和腾讯云的绑定情况</li>
</ul>
<h2 id="小程序开发语言"><a href="#小程序开发语言" class="headerlink" title="小程序开发语言"></a>小程序开发语言</h2><p>小程序的开发语言跟前端开发者比较熟悉的 HTML5 非常相似（甚至相同），小程序的视图层由 <code>WXML</code> 和 <code>WXSS</code> 组成，分别对应 HTML 和 CSS，逻辑层则跟 HTML5 一样，也是 JavaScript 语言实现。</p>
<ul>
<li>WXML：小程序自己发明的 XML 语法描述，用来构造小程序的页面结构</li>
<li>WXSS：小程序的页面的样式表语言，描述 WXML 的样式</li>
<li>JavaScript：小程序 JS 的执行环境并不是普通的 WebView 浏览器，也不是 Node.js 环境，它执行在微信 App 内上下文， 跟 Node.js 一样，也不能像在浏览器内一样对页面 DOM 进行操作</li>
</ul>
<blockquote>
<p>微信小程序运行在三端：iOS、Android 和用于调试的开发者工具</p>
<ul>
<li>在 iOS 上，小程序的 JavaScript 代码运行在 JavaScriptCore 中</li>
<li>在 Android 上，小程序的 JavaScript 代码通过 X5 内核来解析</li>
<li>在 开发工具上， 小程序的 JavaScript 代码运行在 NW.js（Chromium 内核） 中</li>
</ul>
</blockquote>
<p>除了普通的 JavaScript，小程序还支持一种类似 JS 的 <strong>WXS</strong> 语言，WXS 对于小程序开发不是必需的，它的主要目的是为了增强 WXML 的数据处理能力而新引入一种技术实现，其实际解析的语言规范还是 JS，并没有引入新的语法，仅仅对 JS 做了上层的封装和限制，所以学习上基本没什么成本，大致了解下开发文档马上就能上手。本小册实战部分也会涉及简单的 WXS 编写。</p>
<blockquote>
<p>对于 WXS 和 JavaScript 的性能比较，官方给出的数据是：<strong>由于运行环境的差异，在 iOS 设备上小程序内的 WXS 会比 JavaScript 代码快 2 ~ 20 倍。在 Android 设备上二者运行效率无差异。</strong></p>
</blockquote>
<h2 id="小程序项目相关知识"><a href="#小程序项目相关知识" class="headerlink" title="小程序项目相关知识"></a>小程序项目相关知识</h2><h3 id="小程序目录结构"><a href="#小程序目录结构" class="headerlink" title="小程序目录结构"></a>小程序目录结构</h3><p>小程序项目由配置文件、页面文件、静态资源和其他相关（比如组件、小程序云函数等）内容组成，一般小程序会由四类文件组成：</p>
<ul>
<li><code>.json</code> 后缀的 JSON 配置文件</li>
<li><code>.wxml</code> 后缀的 WXML 模板文件</li>
<li><code>.wxss</code> 后缀的 WXSS 样式文件</li>
<li><code>.js</code> 后缀的 JS 脚本逻辑文件</li>
</ul>
<p>小程序项目的目录结构组成没有严格的要求，按照前端项目的经验，一般会分为：配置、页面、静态资源、基础库、组件等多个目录，例如下面的目录结构：</p>
<pre><code>├── app.js            小程序全局app相关js
├── app.json          小程序配置文件
├── app.wxss          小程序全局app样式
├── cloud-functions   云函数目录
│   ├── decrypt
│   ├── geocoder
│   ├── he-air
│   ├── he-weather
│   ├── jscode2session
│   └── weather
├── components        组件库
│   └── icon
├── images            图片等静态资源
│   └── cloud.jpg
├── pages             页面目录
│   ├── diary
│   └── weather
└── project.config.json  工具项目配置文件
</code></pre><p>当然根据不同的项目，可能目录结构不同，但是小程序必需的 <code>app.json</code> 和页面组成是必不可少的。另外，在开发复杂的项目时，我们会用到开发框架或者编译工具，这时候目录结构只需要保证编译之后的目录结构符合规范即可。</p>
<h3 id="小程序的配置"><a href="#小程序的配置" class="headerlink" title="小程序的配置"></a>小程序的配置</h3><p>小程序有三个重要的配置，分别放在三个 JSON 文件内：<code>project.config.json</code>（工具项目配置）、<code>app.json</code>（小程序配置）、<code>page.json</code>（单页面配置）</p>
<ul>
<li><code>project.config.json</code>：这个是配置项目工具相关的，比如开发者工具的编译设置（是否使用 ES6 语法等）、界面设置，以及云函数相关的 <code>cloudfunctionRoot</code>，详细可以参考<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html" target="_blank" rel="noopener">项目配置文件</a></li>
<li><code>app.json</code>：小程序的全局配置，包括了所有页面路径、界面表现、网络超时时间、底部 tab、插件等，常用的两个配置是 <code>window</code> 和 <code>pages</code>，详细配置参考<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/config.html" target="_blank" rel="noopener">全局配置</a></li>
<li><code>page.json</code>：是相对于 <code>app.json</code> 更细粒度的单页面配置，详细参考<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/config.html#%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE" target="_blank" rel="noopener">页面配置</a></li>
</ul>
<h2 id="组件和插件"><a href="#组件和插件" class="headerlink" title="组件和插件"></a>组件和插件</h2><p>小程序页面是由各种组件组成的，组件可以类比成原生 HTML5 的标签。</p>
<h3 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h3><p>小程序内部定义了很多组件，可以对应 HTML5 的标签和基础能力来理解，小程序的组件根据实现不同，可以分为 Web 组件和 Native 组件，Web 组件是由 HTML5 原生 Web 组件封装的组件，比如 <code>view</code>、<code>image</code> 等；Native 组件是为了增强小程序的体验，用客户端技术实现的组件，包括一些交互复杂、原生 Web 组件性能不高的组件，例如 <code>input</code>、<code>map</code>、<code>canvas</code>、<code>video</code> 等。</p>
<p>小程序一共提供 8 大类 30 多个组件：</p>
<ul>
<li>视图容器：主要是实现页面布局的，对常见的布局形式进行了封装，比如滚动 sroll-view 等</li>
<li>基本内容：类似 HTML5 中内容相关的 p、em 等</li>
<li>表单相关：要比 HTML5 的 form 表单丰富一些</li>
<li>导航：类似 a 标签</li>
<li>媒体：类比 HTML5 中的 video、audio 和 img 等，但是提供更标准的界面和更丰富的 API 支持</li>
<li>画布：Native 实现的 Canvas</li>
<li>地图：结合腾讯地图数据 Native 实现的组件</li>
<li>开放能力：这部分组件偏通用和小程序业务</li>
</ul>
<p><strong>view</strong> 代替原来的 <strong>div</strong></p>
<p>block 可以用来占位</p>
<h3 id="自定义组件"><a href="#自定义组件" class="headerlink" title="自定义组件"></a>自定义组件</h3><p>小程序本身支持很多组件，比如地图、按钮等，开发者也可以自己做项目内公共组件，比如我们后面实战部分会介绍做一个 icon 组件，放在 <code>components</code> 目录下面，这样此小程序的任何页面如果要使用这个 icon 公共组件，只需在自己的 <code>page.json</code> 中添加如下字段：</p>
<pre><code class="json">&quot;usingComponents&quot;: {
  &quot;icon&quot;: &quot;../../components/icon/index&quot;
}
</code></pre>
<p>添加完成之后，在页面代码中就可以直接使用 <code>&lt;icon&gt;</code> 的 tag 了。<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/" target="_blank" rel="noopener">官方文档</a>有更加详细的介绍。</p>
<h3 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h3><p>插件是对一组 JS 接口、自定义组件或页面的封装，用于提供给第三方小程序调用。简单来说，插件是组件的升级版本，组件只能在自己项目中使用，插件则更独立，是可以发布到全网，供其他开发者使用的。例如实战中，笔者使用了一款日历插件，则需要在 <code>app.json</code> 中增加 <code>plugins</code> 字段：</p>
<pre><code class="json">&quot;plugins&quot;: {
  &quot;calendar&quot;: {
    &quot;version&quot;: &quot;1.1.3&quot;,
    &quot;provider&quot;: &quot;wx92c68dae5a8bb046&quot;
  }
}
</code></pre>
<p>如果想开发一个插件，则可以参考<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/plugin/development.html" target="_blank" rel="noopener">官方文档</a>。</p>
<h2 id="小程序开发基础"><a href="#小程序开发基础" class="headerlink" title="小程序开发基础"></a>小程序开发基础</h2><p>微信小程序是由数据驱动的开发框架，本小节主要介绍小程序开发中的基础概念和知识。</p>
<h3 id="数据驱动"><a href="#数据驱动" class="headerlink" title="数据驱动"></a>数据驱动</h3><p>微信小程序是数据驱动模型，在 WXML 中可以对页面的数据进行绑定，小程序的 WXML 内使用的是 Mustache 语法，在 <code></code> 内可以将变量名包裹起来。 例如：</p>
<pre><code class="html">&lt;view&gt;{{ message }}&lt;/view&gt;</code></pre>
<pre><code class="js">Page({
  data: {
    message: &#39;Hello MINA!&#39;
  }
})
</code></pre>
<pre><code>但是小程序不支持复杂的表达式，目前支持简单的三元、加 减和逻辑判断，如果要使用形如 `{{parseInt(num)}}` 的函数调用语法，需要 `WXS` 支持：</code></pre><p>WXML 内容：</p>
<pre><code class="html">&lt;wxs src=&quot;./demo.wxs&quot; module=&quot;tools&quot; /&gt;
&lt;view&gt;{{ tools.toNumber(num) }}&lt;/view&gt;</code></pre>
<p>WXS 内容：</p>
<pre><code class="js">// demo.wxs
function toNumber(n){
    return parseInt(n)
}
module.exports.toNumber = toNumber</code></pre>
<p>小程序内对页面的数据修改只能通过 <code>setData</code> 方法，不能使用直接赋值的方式 <code>this.data.key = value</code>：</p>
<pre><code class="js">Page({
    data: {
        message: &#39;Hello MINA!&#39;
    },
    onLoad(){
        this.setData({
            message: &#39;hello world~&#39;
        })
    }
})</code></pre>
<blockquote>
<p>记住：修改页面数据，只能使用 this.setData 修改！</p>
</blockquote>
<h3 id="模板语法"><a href="#模板语法" class="headerlink" title="模板语法"></a>模板语法</h3><pre><code class="html">//逻辑判断
&lt;view wx:if=&quot;{{lenght > 5}}&quot;&gt;&lt;/view&gt;

//字符串运算 花括号和引号之间如果有空格，将最终被解析成为字符串
&lt;view&gt;{{"hello" + name}}&lt;/view&gt; 

//列表渲染
//默认情况只有一层循环时 wx:for-item=&quot;item&quot; wx:for-index=&quot;index&quot; 可以不写 也可以用
&lt;view wx:for=&quot;{{array}}&quot; wx:key=&quot;id&quot;&gt;
    {{index}}: {{item.message}}
&lt;/view&gt;
&lt;script&gt;
    Page({
        data: {
            array: [{id:0,message: &#39;foo&#39;},
                    {id:1,message: &#39;bar&#39;}]
        }
    })
&lt;/script&gt;

//条件渲染
&lt;view wx:if=&quot;{{false}}&quot;&gt;1&lt;/view&gt;
&lt;view wx:elif=&quot;{{true}}&quot;&gt;2&lt;/view&gt;
&lt;view wx:else&gt;3&lt;/view&gt;
//频繁切换时,可以使用hidden,与if相反
&lt;view hidden=&quot;{{ture}}&quot;&gt; True &lt;/view&gt; //通过添加样式display来切换显示</code></pre>
<h3 id="事件绑定和处理"><a href="#事件绑定和处理" class="headerlink" title="事件绑定和处理"></a>事件绑定和处理</h3><p>在小程序内，除了标准 HTML5 中遇见的 <code>touchstart</code> 等事件外，增加了 <code>tap</code> 类的事件，主要包括以下几种：</p>
<table>
<thead>
<tr>
<th>事件名称</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>tap</td>
<td>手指触摸后马上离开</td>
</tr>
<tr>
<td>longpress</td>
<td>手指触摸后，超过 350ms 再离开，如果指定了事件回调函数并触发了这个事件，tap 事件将不被触发</td>
</tr>
<tr>
<td>longtap</td>
<td>手指触摸后，超过 350ms 再离开（推荐使用 longpress 事件代替）</td>
</tr>
</tbody></table>
<p>小程序内，事件的绑定是通过在 WXML 标签增加 <code>bind</code> 属性来实现的， 如 bindtap bindinput bindchange 等 ,比如新鲜天气的生活指数，绑定了一个 <code>tap</code> 事件，当用户点击之后，会响应对应页面 JS 的函数：</p>
<pre><code class="html">&lt;view class=&quot;life-style&quot;&gt;
    &lt;view class=&quot;item&quot; wx:for=&quot;{{lifeStyle}}&quot; data-name=&quot;{{item.name}}&quot; data-detail=&quot;{{item.detail}}&quot; bindtap=&quot;indexDetail&quot;&gt;
        &lt;view class=&quot;title&quot;&gt;
            &lt;icon type=&quot;{{item.icon}}&quot;&gt;&lt;/icon&gt;
            {{item.name}}
        &lt;/view&gt;
        &lt;view class=&quot;content&quot;&gt;{{item.info}}&lt;/view&gt;
    &lt;/view&gt;
&lt;/view&gt;</code></pre>
<p>上面的代码绑定了 <code>tap</code> 事件，事件处理函数为 <code>indexDetail</code>。</p>
<pre><code class="html">//绑定事件时不能带参数,不能带括号,以下为错误写法
&lt;input bindinput=&quot;handleInput(100)&quot; /&gt;
//正确写法
&lt;input bindinput=&quot;handleInput&quot; data-item=&quot;100&quot; /&gt;</code></pre>
<h4 id="事件冒泡"><a href="#事件冒泡" class="headerlink" title="事件冒泡"></a>事件冒泡</h4><p><strong>小程序内的事件分为可冒泡和不可冒泡的事件，除了 submit、input 之类的事件，多数是可冒泡的事件</strong>，对于事件的绑定，除了 <code>bind*</code> 的方式，还可以通过 <code>catch*</code> 的方式来绑定，两者的区别在于：</p>
<ul>
<li><code>bind</code> 不会阻止冒泡，变形写法为 <code>bind:*</code></li>
<li><code>catch</code> 会阻止事件继续冒泡，变形写法为 <code>catch:*</code></li>
</ul>
<h4 id="事件捕获"><a href="#事件捕获" class="headerlink" title="事件捕获"></a>事件捕获</h4><p>小程序内，触摸类事件支持捕获阶段，捕获是先于冒泡的触发，绑定捕获事件，可以使用 <code>capture-bind</code>、<code>capture-catch</code>，后者将中断捕获阶段和取消冒泡阶段，下面是官方的示例：</p>
<blockquote>
<p>在下面的代码中，点击 inner view 会先后调用 handleTap2、handleTap4、handleTap3、handleTap1。</p>
</blockquote>
<pre><code class="html">&lt;view id=&quot;outer&quot; bind:touchstart=&quot;handleTap1&quot; capture-bind:touchstart=&quot;handleTap2&quot;&gt;
    outer view
    &lt;view id=&quot;inner&quot; bind:touchstart=&quot;handleTap3&quot; capture-bind:touchstart=&quot;handleTap4&quot;&gt;
        inner view
    &lt;/view&gt;
&lt;/view&gt;</code></pre>
<blockquote>
<p>如果将上面代码中的第一个 capture-bind 改为 capture-catch，将只触发 handleTap2。</p>
</blockquote>
<pre><code class="html">&lt;view id=&quot;outer&quot; bind:touchstart=&quot;handleTap1&quot; capture-catch:touchstart=&quot;handleTap2&quot;&gt;
    outer view
    &lt;view id=&quot;inner&quot; bind:touchstart=&quot;handleTap3&quot; capture-bind:touchstart=&quot;handleTap4&quot;&gt;
        inner view
    &lt;/view&gt;
&lt;/view&gt;</code></pre>
<p>关于事件的详细说明，建议阅读<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html" target="_blank" rel="noopener">官方的文档</a>，以获取更大的帮助。</p>
<h4 id="事件对象"><a href="#事件对象" class="headerlink" title="事件对象"></a>事件对象</h4><p>当事件触发时，处理函数会响应，传入 <code>event</code> 对象，通过 <code>event</code> 对象可以获取事件触发时候的一些信息，包括时间戳、<code>detail</code> 等。</p>
<p>因为小程序内的事件绑定都是在 WXML 中实现的，所以传递参数只能通过 WXML 上面的属性值来传递，例如下面的代码中，<code>indexDetail</code> 处理函数需要接收生活指数的名称和详情，来弹出弹层提示，这时候需要在标签上增加 <code>data-xx</code> 这样的属性，<code>data-name</code> 和 <code>data-detail</code> 就是两个属性，通过这两个值，可以在 <code>indexDetail</code> 内 <code>event</code> 对象的 <code>target/currentTarget</code> 的 <code>dataset</code> 获取参数。</p>
<pre><code>&lt;view class=&quot;life-style&quot;&gt;
    &lt;view class=&quot;item&quot; wx:for=&quot;{{lifeStyle}}&quot; data-name=&quot;{{item.name}}&quot; data-detail=&quot;{{item.detail}}&quot; bindtap=&quot;indexDetail&quot;&gt;
      &lt;view class=&quot;title&quot;&gt;
        &lt;icon type=&quot;{{item.icon}}&quot;&gt;&lt;/icon&gt;
        {{item.name}}
      &lt;/view&gt;
      &lt;view class=&quot;content&quot;&gt;{{item.info}}&lt;/view&gt;
    &lt;/view&gt;
&lt;/view&gt;</code></pre><pre><code>// weather/index.js
// 响应事件的处理函数
indexDetail(e) {
  const {name, detail} = e.currentTarget.dataset
  wx.showModal({
    title: name,
    content: detail,
    showCancel: false
  })
}</code></pre><p>按照官方文档，<code>target</code> 和 <code>currentTarget</code> 都有个 <code>dataset</code>，<strong>正确获取 <code>dataset</code> 的姿势是使用 <code>currentTarget</code> 的</strong>，但是有时候 <code>target</code> 和 <code>currentTarget</code> 的数据又是完全一样的，如果这里使用 <code>target</code> 的话，那么有时候点击会弹出弹窗，有时候不会弹出，这两者究竟是怎样的关系呢？官方的解释有点模棱两可：</p>
<ul>
<li><code>target</code>：触发事件的源组件</li>
<li><code>currentTarget</code>：事件绑定的当前组件</li>
</ul>
<p>这里笔者做下详细解释：</p>
<ul>
<li><code>target</code>：触发事件的源组件，上面的代码中，<code>target</code> 可能是 <code>view.title</code>、<code>view.content</code>、<code>view.item</code> 任意触发事件的组件</li>
<li><code>currentTarget</code>：事件绑定的当前组件，上面的代码中，只能是真正绑定了 <code>bindtap</code> 的 <code>view.item</code></li>
</ul>
<p>下面再来看下例子：</p>
<pre><code>&lt;view id=&quot;outer&quot; bindtap=&quot;handleTap1&quot;&gt;
  outer view
  &lt;view id=&quot;middle&quot; catchtap=&quot;handleTap2&quot;&gt;
    middle view
    &lt;view id=&quot;inner&quot; bindtap=&quot;handleTap3&quot;&gt;
      inner view
    &lt;/view&gt;
  &lt;/view&gt;
&lt;/view&gt;</code></pre><blockquote>
<p>点击 inner view 时，handleTap3 收到的事件对象 target 和 currentTarget 都是 inner，而 handleTap2 收到的事件对象 target 就是 inner，currentTarget 就是 middle。</p>
<p>由此一看，可以简单总结出来：<code>target</code> 是事件触发源头的地方，即事件开始的地方，可以冒泡到父节点触发父节点的绑定事件；而 <code>currentTarget</code> 是开发者自己绑定事件的地方，即实际的绑定事件的节点。<strong>所以，如果绑定的事件有子节点，那么 <code>target</code> 不会等于 <code>currentTarget</code>，有可能是冒泡触发的，由此可见，获取 <code>dataset</code> 的时候使用 <code>currentTarget</code> 是靠谱的。</strong></p>
</blockquote>
<h3 id="小程序的事件驱动和数据绑定模型"><a href="#小程序的事件驱动和数据绑定模型" class="headerlink" title="小程序的事件驱动和数据绑定模型"></a>小程序的事件驱动和数据绑定模型</h3><p>由上面数据驱动和事件监听的处理方式可见，小程序是一套数据和事件驱动的模型，即下面的形式：</p>
<p><img src="http://helloxx.cn/1653130442bc15fc.jpg" srcset="/myblog/img/loading.gif" alt=""></p>
<p>关于小程序的运行机制，在<a href="https://juejin.im/book/5b70f101e51d456669381803/section/5b70f3456fb9a00986735fa3" target="_blank" rel="noopener">第 4 节</a>中会有更加详细的介绍。</p>
<h3 id="路由"><a href="#路由" class="headerlink" title="路由"></a>路由</h3><p>在小程序内，不能像 HTML5 中 a 标签那样，随便跳转，也不能像 <code>location</code> 对象中对应的属性那样随意跳转，小程序提供了对应 a 标签和 <code>location</code> 对象的方法：<a href="https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html" target="_blank" rel="noopener">navigator 组件</a>和 <a href="https://developers.weixin.qq.com/miniprogram/dev/api/ui-navigate.html" target="_blank" rel="noopener">wx 中的导航相关函数</a>。</p>
<p>在小程序中，路由是由路由栈来维护的，小程序的路由栈中最多维护 5 个页面，这样在 5 个页面内，小程序维护其渲染页面，可以实现快速的切换。</p>
<p>小程序中跳转页面可以通过下面两种方式：</p>
<ol>
<li>使用 navigator 组件：</li>
</ol>
<pre><code>&lt;navigator url=&quot;跳转页面URL&quot; &gt;跳转到新页面&lt;/navigator&gt;
</code></pre><ol start="2">
<li>使用 <code>wx</code> 中的导航相关函数：</li>
</ol>
<pre><code>&lt;view bindtap=&quot;gotoUrl&quot;&gt;跳转页面&lt;/view&gt;
</code></pre><pre><code>Page({
  gotoUrl(){
    let url = &#39;pages/another/url&#39;
    wx.navigateTo({
      url
    })
  }
})
</code></pre><h3 id="JavaScript-的限制和增强"><a href="#JavaScript-的限制和增强" class="headerlink" title="JavaScript 的限制和增强"></a>JavaScript 的限制和增强</h3><p>微信内的 JavaScript 相对于浏览器中的有限制也有增强，增强的部分是基于小程序 Native 端能力做的增强，比如增强的文件操作类（相册、录音等）；除了增强，跟 HTML5 浏览器环境最大的不同是限制部分。</p>
<p>小程序的执行环境是没有浏览器了，所以浏览器环境特有的 <code>window</code> 对象、BOM 和 DOM 等相关 API 都存在缺失（有对应的补充 API），小程序的执行环境是类似于 Node.js 的一种执行环境。因为没有浏览器环境，所以跟浏览器相关的操作如 cookie、Ajax 请求（<code>XMLHttpRequest</code>）、DOM 选择器、DOM 事件、路由 history、缓存、设备信息、位置等都不存在，与之相对应的是小程序的私有 API，比如我们在小程序中不能使用 <code>XMLHttpRequest</code>，但是可以使用功能更加强大的 <code>wx.request</code> 方法。</p>
<h2 id="小程序布局相关知识"><a href="#小程序布局相关知识" class="headerlink" title="小程序布局相关知识"></a>小程序布局相关知识</h2><p>本小节介绍小程序布局相关的知识。</p>
<h3 id="rpx"><a href="#rpx" class="headerlink" title="rpx"></a>rpx</h3><p>小程序 WXSS 中使用了 rpx 这个长度单位，可以用于表示元素的宽高和边距、字体的大小等。对于习惯使用 px 或者 rem 来做页面的前端来说，这可能让人有点迷糊。rpx 是以小程序容器宽度（等于设备宽度）恒等于 <code>750rpx</code> 来做定义的。对于 iPhone 6 来说，因为 dpr 为 2，所以 iPhone 的宽度为 375px，这样在 iPhone 6 上使用 rpx 的话，换算关系为 <code>2rpx=1px</code>。根据这样的关系类推，得到官方给的表格：</p>
<table>
<thead>
<tr>
<th>设备</th>
<th>rpx换算 px (屏幕宽度/750)</th>
<th>px 换算 rpx (750/屏幕宽度)</th>
</tr>
</thead>
<tbody><tr>
<td>iPhone 5</td>
<td>1rpx = 0.42px</td>
<td>1px = 2.34rpx</td>
</tr>
<tr>
<td>iPhone 6</td>
<td>1rpx = 0.5px</td>
<td>1px = 2rpx</td>
</tr>
<tr>
<td>iPhone 6 Plus</td>
<td>1rpx = 0.552px</td>
<td>1px = 1.81rpx</td>
</tr>
</tbody></table>
<p>看起来很麻烦，但是只需要<strong>按照官方建议，让设计师按照 iPhone 6 的视觉稿标准出图即可，即宽度为 750px</strong></p>
<blockquote>
<p> 使⽤步骤： </p>
<ol>
<li>确定设计稿宽度 pageWidth </li>
<li>计算⽐例 750rpx = pageWidth px ,因此 1px=750rpx/pageWidth 。 </li>
<li>在less⽂件中，只要把设计稿中的 px =&gt; 750/pageWidth rpx 即可。 </li>
</ol>
</blockquote>
<h3 id="样式"><a href="#样式" class="headerlink" title="样式"></a><strong>样式</strong></h3><p> wxss中支持样式导⼊</p>
<p>使⽤ @import 语句可以导入外联样式表，只支持相对路径。</p>
<p> 小程序不支持通配符 <code>*</code></p>
<h3 id="flex-布局"><a href="#flex-布局" class="headerlink" title="flex 布局"></a>flex 布局</h3><p>在 HTML5 标准中，flex 布局可以简便、完整、响应式地实现各种页面布局，小程序作为晚于 flex 标准创建的 Hybrid 解决方案，自然在布局设计上采用了先进的 flex 布局。</p>
<p>关于 flex 布局相关知识，可以参考阮一峰的 <a href="http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html" target="_blank" rel="noopener">flex 布局教程：语法篇</a>和<a href="http://www.ruanyifeng.com/blog/2015/07/flex-examples.html" target="_blank" rel="noopener">实例篇</a>。</p>
<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本节从小程序账号注册说起，依次介绍了小程序开发者工具、小程序开发语言、小程序项目、开发和布局相关的基础知识，内容由浅到深，全面地帮助大家入门小程序开发。</p>
<p>更多微信小程序的基础知识可以参考官方文档：<a href="https://developers.weixin.qq.com/miniprogram/dev/index.html" target="_blank" rel="noopener">简易教程</a>、<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/MINA.html" target="_blank" rel="noopener">框架</a>、<a href="https://developers.weixin.qq.com/miniprogram/dev/component/" target="_blank" rel="noopener">组件</a>、<a href="https://developers.weixin.qq.com/miniprogram/dev/api/" target="_blank" rel="noopener">API</a>。</p>
<h1 id="基础篇-2：小程序·云开发基础知识"><a href="#基础篇-2：小程序·云开发基础知识" class="headerlink" title="基础篇 2：小程序·云开发基础知识"></a>基础篇 2：小程序·云开发基础知识</h1><blockquote>
<p><strong><a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html" target="_blank" rel="noopener">小程序·云开发</a></strong> 是微信团队联合腾讯云团队推出的一套小程序开发解决方案。小程序·云开发为开发者提供完整的云端流程，弱化后端和运维概念，开发者无需购买和管理底层计算资源，包括服务器、数据库、静态存储，只需使用平台提供的简易 API 进行核心业务等开发，实现快速上线和迭代，把握业务发展的黄金时期。</p>
</blockquote>
<p>简单来说，小程序开发中用到的服务器、数据库和静态资源管理，都可以托管到「小程序·云开发」上，小程序开发者只需要关注业务功能实现，而不需要关心服务器运维等带来的问题。小程序开发主要用到的是前端技术，后端开发和服务运维对于前端开发者来说有一定的门槛，而小程序·云开发的出现就是解决这个问题的。</p>
<h2 id="小程序·云开发特点"><a href="#小程序·云开发特点" class="headerlink" title="小程序·云开发特点"></a>小程序·云开发特点</h2><ul>
<li>提供完整后端服务解决方案，包括数据库、静态资源管理和云函数（功能服务）</li>
<li>背靠腾讯云大平台，腾讯云丰富的 API 和功能都可以简单调用</li>
<li>对于普通开发者，<strong>完全免费</strong></li>
<li>对于高级服务型小程序，提供更强服务支持、计费弹性、不使用不计费的特点</li>
<li>无服务器搭建，无域名配置，直接调用 API 使用</li>
</ul>
<p>对于我们普通开发者来说，小程序·云开发是个不错的练手平台。下面详细介绍下小程序·云开发提供的功能。</p>
<p>小程序·云开发提供数据库、云函数和静态存储三大功能，还有小程序的用户管理功能，在用户管理界面可以轻松掌握小程序的授权用户情况。目前云开发的这些功能已经深度整合到「微信开发者工具 Beta 版」中，可以在顶部点击「云开发」进入。</p>
<p>首页会看到一些 API 调用和小程序·云开发的资源配置信息。</p>
<h2 id="申请小程序·云开发"><a href="#申请小程序·云开发" class="headerlink" title="申请小程序·云开发"></a>申请小程序·云开发</h2><p>如果还没有开通小程序·云开发账号，首次点击「云开发」会出现云开发控制台介绍界面，该界面主要是云开发的介绍和申请入口。</p>
<p>点击蓝色的「创建资源环境」按钮进入新建环境界面。</p>
<p>第二步出现选择套餐信息，现在公测阶段只有一个免费套餐选项，将来应该会有更多套餐选择。填写上「环境名称」点击确认之后，会使用小程序开发账号在腾讯云开通一个 <code>fake account</code>。到此小程序·云开发就开通了！</p>
<blockquote>
<p><strong>TIPS</strong>：目前一个小程序账号可以免费开通两个云开发账号，免费版本的限制应该也要注意：</p>
<ul>
<li>数据库存储空间：1GB</li>
<li>文件存储空间：5GB</li>
<li>文件存储外网下行流量：5GB/月</li>
<li>云函数数量：20</li>
<li>云函数资源使用量：10万GBs/月</li>
<li>API调用次数：3万次/天</li>
</ul>
<p><strong>这些对于我们做普通的小程序开发练习已经足够了！</strong></p>
</blockquote>
<h3 id="小程序·云开发在小程序中的调用"><a href="#小程序·云开发在小程序中的调用" class="headerlink" title="小程序·云开发在小程序中的调用"></a>小程序·云开发在小程序中的调用</h3><p>小程序·云开发可以在小程序中直接通过调用 <code>wx.cloud.*</code> 的方式进行调用，在调用云开发 API 之前，需要先调用 <code>wx.cloud.init</code> 对云开发进行初始化：</p>
<pre><code>wx.cloud.init({
  env: &#39;tianqi-xxx&#39;
})
</code></pre><p>初始化时，需要传入 <code>env</code> 参数，该参数为创建小程序·云开发时的<code>环境 ID</code>，可以在「云开发」页面右上角「当前环境」下拉菜单中找到：</p>
<p><img src="http://helloxx.cn/1653133b981bd767.jpg" srcset="/myblog/img/loading.gif" alt=""></p>
<p>下面详细介绍下小程序·云开发的三大功能。</p>
<h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><p>小程序·云开发的数据库是一种 NoSQL 云端数据库，数据以 JSON 格式存储，在底层支持弹性可扩展、自动容灾、监控管理，所以开发者不需要关注数据库的运维。从提供的文档和接口来看，云开发的数据库应该是由 MongoDB 实现的。</p>
<p>每个数据库是由多个集合（collection，类比关系型数据库中表的概念）组成，集合有多个 JSON 文档（行）组成，NoSQL 的特点是没有固定的字段，所以整个集合可以看成一个大的 JSON 数组，一个集合在数据库中的存储格式如下：</p>
<pre><code>[
  {
    &quot;_id&quot;: 1,
    &quot;_openid&quot;: &quot;ax123CVadb&quot;,
    &quot;name&quot;: &quot;Alice&quot;,
    &quot;city&quot;: &quot;Guangzhou&quot;
  },
  {
    &quot;_id&quot;: 2,
    &quot;_openid&quot;: &quot;xj372nJdfa&quot;,
    &quot;name&quot;: &quot;Bob&quot;,
    &quot;city&quot;: &quot;Shenzhen&quot;
  }
]
</code></pre><p>需要说明的是，文档中的 <code>_id</code> 是唯一的，开发者可以插入数据的时候自定义，另外 <code>_openid</code> 是增加文档默认创建的，代表当前小程序用户的唯一标示，后面实战部分笔者会重点介绍小程序的用户授权相关的内容。</p>
<h3 id="快速入门"><a href="#快速入门" class="headerlink" title="快速入门"></a>快速入门</h3><p>详细介绍可以见小程序·云开发的使用文档，这里笔者整理出常用的 API 使用方法：</p>
<pre><code>// 初始化
wx.cloud.init({
  env: &#39;tianqi-xxx&#39;
})
// 获取数据库实例
const db = wx.cloud.database()
// 增
db.collection(&#39;集合名称&#39;).add({
  data: {} // 插入的数据
}).then(res =&gt; {
  // 可以通过 res._id 获取创建的记录的 id
  console.log(res._id)
})
// 删
db.collection(&#39;集合名称&#39;).doc(&#39;文档 ID&#39;).remove().then(res =&gt; {
  console.log(&#39;removed&#39;)
})
// 改
db.collection(&#39;集合名称&#39;).doc(&#39;文档 ID&#39;).update({
  data: {
    title: &#39;我的第 1 篇文章&#39;, // 只更新 title 字段，其他不更新
  }
}).then(res =&gt; {
  // 可以通过 res._id 获取创建的记录的 id
  console.log(res._id)
})
// 查
db.collection(&#39;集合名称&#39;).doc(&#39;文档 ID&#39;).get().then(res =&gt; {
  // 打印结果，res.data 即为记录的数据
  console.log(res)
})
const _ = db.command // 取指令
db.collection(&#39;集合名称&#39;).where({
  // 查找条件
  category: &#39;computer&#39;,
  properties: {
    memory: _.gt(8), // 表示大于 8
  }
})
</code></pre><p>小程序·云开发的数据库查询命令是可以使用查询筛选指令的，使用查询筛选指令可以缩小查询范围，找到查询条件的文档。</p>
<p>以下指令挂载在 <code>db.command</code> 下</p>
<p>类型</p>
<p>接口</p>
<p>说明</p>
<p>比较运算</p>
<p>eq</p>
<p>字段 ==</p>
<p>neq</p>
<p>字段 !=</p>
<p>gt</p>
<p>字段 &gt;</p>
<p>gte</p>
<p>字段 &gt;=</p>
<p>lt</p>
<p>字段 &lt;</p>
<p>lte</p>
<p>字段 &lt;=</p>
<p>in</p>
<p>字段值在数组里</p>
<p>nin</p>
<p>字段值不在数组里</p>
<p>逻辑运算</p>
<p>and</p>
<p>表示需同时满足指定的所有条件</p>
<p>or</p>
<p>表示需同时满足指定条件中的至少一个</p>
<blockquote>
<p>举例说明：在 <code>diary</code> 集合中找出 <code>openid</code> 某个值并且创建时间（<code>tsModified</code>）在 <code>start</code> 和 <code>end</code> 之间的文档。</p>
</blockquote>
<pre><code>db
  .collection(&#39;diary&#39;)
  .where({
    openid,
    tsModified: _.gte(start).and(_.lt(end))
  })
  .get()
</code></pre><h3 id="数据库的索引"><a href="#数据库的索引" class="headerlink" title="数据库的索引"></a>数据库的索引</h3><p>增加合适的索引可以提升文档的查找效率，比如根据时间、用户 ID 查找，可以将时间和用户 ID 字段设置为索引项，笔者在使用的时候发现：<strong>在小程序·云开发管理后台并不能对某个字段增加唯一索引。</strong></p>
<h3 id="自带权限管理"><a href="#自带权限管理" class="headerlink" title="自带权限管理"></a>自带权限管理</h3><p>在研发中，经常会针对不同的用户设置不同的数据库权限，例如：某条记录是用户 A 创建的，则只有用户 A 可以删除或者更新，其他用户只有查看的权限，这样的需求很常见，实际开发起来却非常费劲，往往要写不少权限判断的代码，小程序·云开发的数据库支持「权限管理」功能，可以针对这类需求对不同的集合进行统一处理，大大降低开发的门槛！</p>
<p>集合的操作权限包括以下四种：</p>
<ul>
<li>所有用户可读，仅创建者及管理员可写</li>
<li>仅创建者及管理员可读写</li>
<li>所有用户可读，仅管理员可写</li>
<li>仅管理员可读写</li>
</ul>
<p>可以在「云开发 -&gt; 数据库 -&gt; 选择某个集合 -&gt; 权限设置」页面进行设置。</p>
<p><img src="http://helloxx.cn/1653133f8470acf5.jpg" srcset="/myblog/img/loading.gif" alt=""></p>
<h2 id="文件存储"><a href="#文件存储" class="headerlink" title="文件存储"></a>文件存储</h2><p>小程序·云开发的文件存储功能是专为存储和提供用户生成的内容（如图片或视频）的开发者打造的。开发者可使用腾讯云的 SDK 来存储图片、音频、视频或其他由用户生成的内容。在小程序内，则可以通过云开发的 API 直接上传、下载和管理存储。</p>
<p>公共使用的静态资源，可以通过「云开发 -&gt; 存储」界面直接上传和管理，上传之后，就可以在界面内找到资源的 CDN 地址。</p>
<p>而对于小程序内需要上传和管理的则通过下面几个 API 来实现：</p>
<pre><code>// 上传，上传后会返回资源的 ID
wx.cloud.uploadFile
// 下载
wx.cloud.downloadFile
// 根据资源 ID 获取资源访问地址
wx.cloud.getTempFileURL
// 根据资源 ID 列表删除某资源
wx.cloud.removeFile
</code></pre><p>跟数据库权限管理一样，如果需要用户自己上传的内容自己可以管理，那么需要设置存储的操作权限，在「云开发 -&gt; 存储」下可以设置全局的存储操作权限。</p>
<h2 id="云函数"><a href="#云函数" class="headerlink" title="云函数"></a>云函数</h2><p>云函数是腾讯云提供的一套函数计算解决方案，我们可以将每个功能 API 做成单个可执行的函数，然后放到腾讯云上去托管，每个云函数是相互独立可执行的。代码编写完成后放到云端，不执行不调用不收费，执行调用按照调用次数和 CPU 等计算资源的占用情况收费。有了云函数，开发者无须搭建和购买服务器，只需要将写好的云函数代码上传部署到腾讯云，即可以在小程序内通过 <code>wx.cloud.callFunction</code> 的方法进行调用。</p>
<h3 id="快速入门-1"><a href="#快速入门-1" class="headerlink" title="快速入门"></a>快速入门</h3><h4 id="创建云函数"><a href="#创建云函数" class="headerlink" title="创建云函数"></a>创建云函数</h4><p>创建云函数有两种方式，一种是直接在小程序开发者工具中进行操作：</p>
<p>进入「云开发 -&gt; 云函数 -&gt; 添加」创建</p>
<p>另外一种是直接在小程序开发者工具中上传，在上传之前需要配置小程序的 <code>project.config.json</code>，指明哪个路径为云函数目录：</p>
<pre><code>{
  &quot;cloudfunctionRoot&quot;: &quot;./cloud-functions/&quot;
}
</code></pre><p>配置完成后，在小程序开发者工具的编辑器中展开 <code>cloud-functions</code> 文件夹，选择对应的云函数文件夹，右键选择「上传并部署」即可：</p>
<p><img src="http://helloxx.cn/16531351a0e7da9f.jpg" srcset="/myblog/img/loading.gif" alt=""></p>
<h4 id="写个简单云函数"><a href="#写个简单云函数" class="headerlink" title="写个简单云函数"></a>写个简单云函数</h4><p>小程序·云开发的云函数是执行在 Node.js 8.9.0 版本下的，云函数必须在 index.js 有 <code>main</code> 方法为入口，例如下面的代码：</p>
<pre><code class="js">// 命名为 test 的函数内容 index.js
exports.main = async (event, context) =&gt; {
  let {a, b} = event
  return new Promise((resolve, reject) =&gt; {
    resolve({result: parseInt(a) + parseInt(b)})
  })
}
</code></pre>
<p>除了上面 Promise 的写法，还可以用回调的方式：</p>
<pre><code class="js">// 命名为 test 的函数内容 index.js
// 回调方式 callback
exports.main = async (event, context, callback) =&gt; {
  let {a, b} = event
  callback(null, {result: parseInt(a) + parseInt(b)})
}
</code></pre>
<blockquote>
<p>TIPS:</p>
<ol>
<li>云函数如果不存在 <code>main</code> 的方法，上传部署的时候会报错！</li>
<li>云函数回调方式写法遵循「错误优先」原则（Error-First Callback）。</li>
</ol>
</blockquote>
<p>云函数接受两个 JSON 格式的参数 <code>event</code> 和 <code>context</code>，两者分别代表：</p>
<ul>
<li><code>event</code>：平台将 <code>event</code> 入参传递给执行方法，通过此 <code>event</code> 入参对象，代码将与触发函数的事件（event）交互，<code>event</code> 可以获取 <code>wx.cloud.callFunction</code> 调用的参数 <code>data</code></li>
<li><code>context</code>：平台将 <code>context</code> 入参传递给执行方法，通过此 <code>context</code> 入参对象，代码将能了解到运行环境及当前请求的相关内容</li>
</ul>
<blockquote>
<p>TIPS： 开发者可以在云函数内获取到每次调用的上下文（appId、openId 等），无需维护复杂的鉴权机制，即可获取天然可信任的用户登录态（openId），这俩值可以从<code>event.userInfo</code>中读取。</p>
</blockquote>
<p>上面 <code>test</code> 的函数上传到腾讯云之后，我们在小程序的 JS 代码中可以使用下面的方法进行调用：</p>
<pre><code class="js">wx.cloud.callFunction({
  name: &#39;test&#39;,
  data: {
    a: 1,
    b: 2
  }
}).then(r=&gt;{
  // 因为 main 的方法实际是个 promisify 的返回，所以可以直接使用 then/catch
  console.log(r)
}).catch(e=&gt;{
  console.log(e)
})
</code></pre>
<h4 id="云函数的依赖管理"><a href="#云函数的依赖管理" class="headerlink" title="云函数的依赖管理"></a>云函数的依赖管理</h4><p>在云函数中，可以像正常的 Node.js 一样，使用 <code>package.json</code> 和 <code>node_modules</code> 来对依赖进行管理，在开发完代码之后，需要将 <code>node_modules</code> 文件夹一起上传到云端去。下面笔者将带着大家做一个什么值得买的简单抓取的云函数，讲解函数编写、npm 模块使用、云函数本地测试整个流程。</p>
<p>先说下，笔者要实现的功能：</p>
<ol>
<li>根据传入的分类参数，获取什么值得买对应分类的最新文章内容</li>
<li>提取出文章列表的 title、image、mall，即文章名称、文章的配图和优惠所属的网站</li>
</ol>
<p>首先在 cloud-functions 文件夹（该文件夹是我们创建的云函数文件夹），创建一个 <code>smzdm</code> 的云函数，目录结构如下：</p>
<pre><code>cloud-functions
├── smzdm
│   ├── index.js
</code></pre><p>然后进入 <code>smzdm</code> 目录，执行 <code>npm init</code>，按照提示填写内容以后，会在该目录下生成 <code>package.json</code> 文件，接下来需要安装抓取「什么值得买」手机站点的两个 npm 模块：</p>
<ul>
<li><a href="https://www.npmjs.com/package/request" target="_blank" rel="noopener">request</a>：用于做数据请求，抓取站点的 HTML 内容</li>
<li><a href="https://www.npmjs.com/package/cheerio" target="_blank" rel="noopener">cheerio</a>：用于将 HTML 结构数据转为类似 jQuery 的对象，可以通过 CSS 选择器对 HTML 的内容进行提取</li>
</ul>
<p>安装命令如下：</p>
<pre><code># 进入 smzdm 目录，执行
npm install --save request cheerio
</code></pre><p>安装之后的目录结构如下：</p>
<pre><code>cloud-functions
├── smzdm
│   ├── node_modules
│   ├── index.js
│   └── package.json
</code></pre><p>然后我们开始编写 index.js 内容，第一步引入模块，编写 <code>main</code> 方法：</p>
<pre><code class="js">// 引入 requst 和 cheerio
const request = require(&#39;request&#39;)
const cheerio = require(&#39;cheerio&#39;)
exports.main = async (event = {}) =&gt; {
  // 获取具体什么值得买网站分类
  let category = event.category || &#39;diannaoshuma&#39;
  return new Promise((resolve, reject) =&gt; {

  })
}
</code></pre>
<p>第二步，开始编写具体的逻辑，即使用 <code>request.get</code> 先获取 HTML 内容，然后使用 <code>cheerio</code> 将 HTML 内容进行结构化，经过使用 Chrome 查看器查看，发现最新文章都包含在一个 <code>class</code> 为 <code>card-group-list</code> 的 div 下，然后找到 <code>zm-card-title</code> 等每个文章的标题、图片和商城信息，将结果放到一个数组，最后 <code>resolve</code> 输出：</p>
<pre><code class="js">const request = require(&#39;request&#39;)
const cheerio = require(&#39;cheerio&#39;)
exports.main = async (event = {}) =&gt; {
  let category = event.category || &#39;diannaoshuma&#39;
  return new Promise((resolve, reject) =&gt; {
    request.get(`https://m.smzdm.com/fenlei/${category}/`, (e, req, body) =&gt; {
      if (!e &amp;&amp; req.statusCode === 200) {
        const $ = cheerio.load(body)
        const result = []
        $(&#39;.card-group-list&#39;).each((i, v) =&gt; {
          let $v = $(v)
          let title = $v.find(&#39;.zm-card-title&#39;).text().trim()
          let image = $v.find(&#39;.zm-card-media img&#39;).attr(&#39;src&#39;)
          let mall = $v.find(&#39;.card-mall&#39;).text().trim()
          result.push({
            title,
            image,
            mall
          })
        })
        resolve(result)
      }
    })
  })
}
</code></pre>
<p>这样就完成了一个带有依赖模块的云函数编写，上传到腾讯云部署之后，在小程序中使用：</p>
<pre><code>wx.cloud.callFunction({
  name: &#39;smzdm&#39;,
  data: {
    category: &#39;diannaoshuma&#39;
  }
}).then(r=&gt;{console.log(r)})
</code></pre><h4 id="云函数的调试"><a href="#云函数的调试" class="headerlink" title="云函数的调试"></a>云函数的调试</h4><p>云函数有一个很不方便的地方，就是测试起来相对来说比较麻烦，我们不能每次都上传到云端，通过 <code>wx.cloud.callFunction</code> 的方式进行调用，下面介绍几种测试的方法。</p>
<blockquote>
<ol>
<li>线下：函数自己使用 Node 来测试</li>
</ol>
</blockquote>
<p>这种方法就是在 index.js 的最后，增加一个测试方法，比如：</p>
<pre><code>const request = require(&#39;request&#39;)
const cheerio = require(&#39;cheerio&#39;)
const main = (exports.main = async (event = {}) =&gt; {
  let category = event.category || &#39;diannaoshuma&#39;
  return new Promise((resolve, reject) =&gt; {
    request.get(`https://m.smzdm.com/fenlei/${category}/`, (e, req, body) =&gt; {
      if (!e &amp;&amp; req.statusCode === 200) {
        const $ = cheerio.load(body)
        const result = []
        $(&#39;.card-group-list&#39;).each((i, v) =&gt; {
          let $v = $(v)
          let title = $v.find(&#39;.zm-card-title&#39;).text().trim()
          let image = $v.find(&#39;.zm-card-media img&#39;).attr(&#39;src&#39;)
          let label = $v.find(&#39;.card-label&#39;).text().trim()
          let mall = $v.find(&#39;.card-mall&#39;).text().trim()
          result.push({
            title,
            image,
            label,
            mall
          })
        })
        resolve(result)
      }
    })
  })
})
main({category: &#39;diannaoshuma&#39;}).then(r=&gt;{console.log(r)})
</code></pre><p>然后使用 Node.js 直接运行该文件：<code>node index.js</code></p>
<blockquote>
<ol start="2">
<li>线上：使用开发者工具</li>
</ol>
</blockquote>
<p>在小程序开发者工具的云开发控制台内有测试的工具，进入路径为：「云开发 -&gt; 云函数列表 -&gt; 点击具体云函数 -&gt; 右上角测试」。</p>
<p>对于测试的参数，还可以保存下来模板，方便以后使用。</p>
<p><img src="http://helloxx.cn/1653135a895f19a7.jpg" srcset="/myblog/img/loading.gif" alt=""></p>
<h4 id="云函数的-mock-server"><a href="#云函数的-mock-server" class="headerlink" title="云函数的 mock server"></a>云函数的 mock server</h4><p>上文提到的测试方法，都是将小程序的研发流程完全割裂开来，不能完整地测试小程序的代码，只能要么测试云函数要么测试小程序，那么测试小程序的代码就需要上线云函数，实际还是一种效率不高的做法。这里笔者介绍一种本地 mock server 的方式来开启云函数的测试，这种方式可以打通小程序开发的整个流程。具体做法分两步：</p>
<ol>
<li>将云函数作为一个接受请求参数的 server 来访问</li>
<li>使用 <code>wx.request</code> 构造请求拿到云函数的处理结果，然后跑通整个研发流程</li>
</ol>
<p>笔者在 mock server 选型上，选择使用 <a href="https://www.expressjs.com/" target="_blank" rel="noopener">Express</a> 自建 mock server 的方式。</p>
<p>首先，启动一个 Express server：</p>
<pre><code>const express = require(&#39;express&#39;)
const app = express()

app.listen(3000, () =&gt; {
  console.log(`开发服务器启动成功：http://127.0.0.1:3000`)
})
</code></pre><p>然后将云函数引入进程序中来，作为一个路由 handler 来接受http url 的参数，处理请求之后，将返回的处理结果通过 Express 的 <code>res.json</code>输出。</p>
<pre><code>const test = require(&#39;./cloud-functions/test/&#39;).main

app.get(&#39;/api/test&#39;, (req, res, next) =&gt; {
  // 将 req.query 传入
  test(req.query).then(res.json.bind(res)).catch((e) =&gt; {
    console.error(e)
    next(e)
  })
  // next()
})
</code></pre><p>这样访问 <code>http://127.0.0.1:3000/api/test?a=1&amp;b=2</code> 就会输出结果了。到这里笔者就将 mock server 搭建完毕了。</p>
<p>关于小程序代码中如何调用，怎么保持代码的一致性，自由切换线上代码和线下开发代码的方式，在<a href="https://juejin.im/book/5b70f101e51d456669381803/section/5b70f587518825612b15bd95" target="_blank" rel="noopener">实战篇 1：小程序开发环境搭建</a>中会有更详细的讲解。</p>
<h2 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h2><p>本节重点介绍了腾讯新推出的小程序云开发的基础知识，云开发由 NoSQL 数据库、文件存储和云函数三个云产品组成，其中数据库和文件存储都可以单独设置用户权限，这样极大地降低了 UGC（用户产生内容）类小程序的开发门槛。</p>
<p>云函数是小程序·云开发中一个很重要的产品，本节介绍了云函数的基本用法和注意事项，同时针对小程序云函数的开发测试比较困难的情况，提出了 mock server 的解决方式。</p>

</div>

              </div>
            </div>
          </div>
        </div>
      </div>
    
  </main>

  
    <a class="z-depth-1" id="scroll-top-button" href="#" role="button">
      <i class="fa fa-chevron-up scroll-top-arrow" aria-hidden="true"></i>
    </a>
  

  
    <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
  

  

  

  <footer class="mt-5">
  <div class="text-center py-3">
    <div>
      <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><b>Hexo</b></a>
      <i class="iconfont icon-love"></i>
      <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"> <b>Fluid</b></a>
    </div>
    

    

    
  </div>
</footer>

<!-- SCRIPTS -->
<script  src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js" ></script>
<script  src="https://cdn.staticfile.org/popper.js/1.16.1/umd/popper.min.js" ></script>
<script  src="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/js/bootstrap.min.js" ></script>
<script  src="https://cdn.staticfile.org/mdbootstrap/4.13.0/js/mdb.min.js" ></script>
<script  src="/myblog/js/main.js" ></script>


  <script  src="/myblog/js/lazyload.js" ></script>





  <script defer src="https://cdn.staticfile.org/clipboard.js/2.0.6/clipboard.min.js" ></script>
  <script  src="/myblog/js/clipboard-use.js" ></script>








<!-- Plugins -->



  <script  src="https://cdn.staticfile.org/prettify/188.0.0/prettify.min.js" ></script>
  <script>
    $(document).ready(function () {
      $('pre').addClass('prettyprint  linenums');
      prettyPrint();
    })
  </script>



  <script  src="https://cdn.staticfile.org/typed.js/2.0.11/typed.min.js" ></script>
  <script>
    var typed = new Typed('#subtitle', {
      strings: [
        '  ',
        "微信小程序学习笔记&nbsp;",
      ],
      cursorChar: "_",
      typeSpeed: 70,
      loop: false,
    });
    typed.stop();
    $(document).ready(function () {
      $(".typed-cursor").addClass("h2");
      typed.start();
    });
  </script>



  <script  src="https://cdn.staticfile.org/anchor-js/4.2.2/anchor.min.js" ></script>
  <script>
    anchors.options = {
      placement: "right",
      visible: "hover",
      
    };
    var el = "h1,h2,h3,h4,h5,h6".split(",");
    var res = [];
    for (item of el) {
      res.push(".markdown-body > " + item)
    }
    anchors.add(res.join(", "))
  </script>



  <script  src="/myblog/js/local-search.js" ></script>
  <script>
    var path = "/myblog/local-search.xml";
    var inputArea = document.querySelector("#local-search-input");
    inputArea.onclick = function () {
      getSearchFile(path);
      this.onclick = null
    }
  </script>



  <script defer src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>
  <script>
    $("#post img:not(.no-zoom img, img[no-zoom])").each(
      function () {
        var element = document.createElement("a");
        $(element).attr("data-fancybox", "images");
        $(element).attr("href", $(this).attr("src"));
        $(this).wrap(element);
      }
    );
  </script>












</body>
</html>
